package ui;

/*********************************************************************************

 Creates a random maze, then solves it by finding a path from the
 upper left corner to the lower right corner.  (After doing
 one maze, it waits a while then starts over by creating a
 new random maze.)

 This applet is cutomizable using the following parameters in
 its <applet> tag:

 name            default value    meaning
 ------           ---------------  --------------------------------------
 rows                     21         number of rows in maze (must be odd)
 columns                  21         number of columns in maze (must be odd)
 border                    0         width of colored border around maze
 (extra space after making equal-sized
 rows and columns is also part of the border)
 sleepTime              5000         pause, in milliseconds between
 solving one maze and creating
 another
 speed                     3         integer between 1 and 5 specifying
 how fast the a maze is created
 and solved.  (1 is fastest.)
 wallColor             black  
 emptyColor      128 128 255
 pathColor           200 0 0      
 visitedColor  same as empty
 borderColor           white

 Parameter names are case sensative.  Parameters that specify
 colors can be given by one of the predefined color names
 (black, white, red, blue, green, cyan, magenta, yellow, pink
 orange, gray, lightGray, darkGray) or as an RGB color specified
 by three integers between 0 and 255.  Color names are NOT
 case sensative.

 BY:  David Eck
 Department of Mathematics and Computer Science
 Hobart and William Smith Colleges
 Geneva, NY   14456

 E-mail:  eck@hws.edu


 NOTE:  YOU CAN DO ANYTHING YOU WANT WITH THIS CODE AND APPLET, EXCEPT
 CLAIM CREDIT FOR THEM (such as by trying to copyright the code
 your self).

 **************************************************************************/
import java.awt.Color;
import java.awt.Graphics;
import maze.Maze;
import maze.Mazes;

public class MazeApplet extends java.applet.Applet implements Runnable {
	int[][] maze; // Description of state of maze. The value of maze[i][j]
	// is one of the constants wall, pathcode, empty,
	// or visited. (Value can also be negative, temporarily,
	// inside createMaze().)
	// A maze is made up of walls and corridors. maze[i][j]
	// is either part of a wall or part of a corridor. A cell
	// cell that is part of a cooridor is represented by path
	// if it is part of the current path through the maze, by
	// visited if it has already been explored without finding
	// a solution, and by empty if it has not yet been explored.
	final static int backgroundCode = 0;
	// the next six items are set up in init(), and can be specified
	// using applet parameters
	Color[] color = new Color[5]; // colors associated with the preceding 5
									// constants;
	int rows = 35; // number of rows of cells in maze, including a wall around
					// edges
	int columns = 35; // number of columns of cells in maze, including a wall
						// around
						// edges
	int border = 6; // minimum number of pixels between maze and edge of applet
	int sleepTime = 5000; // wait time after solving one maze before making
							// another
	int speedSleep = 50; // short delay between steps in making and solving maze
	Thread mazeThread; // thread for creating and solving maze
	Graphics g = null; // graphics context for applet; created in checkSize()
	int width = -1; // width of applet, to be set by checkSize()
	int height = -1; // height of applet, to be set by checkSize()
	int totalWidth; // width of applet, minus border area (set in checkSize())
	int totalHeight; // height of applet, minus border area (set in checkSize())
	int left; // left edge of maze, allowing for border (set in checkSize())
	int top; // top edge of maze, allowing for border (set in checkSize())
	boolean mazeExists = false; // set to true when maze[][] is valid; used in

	// redrawMaze(); set to true in createMaze(), and
	// reset to false in run()
	Integer getIntParam(String paramName) {
		// Utility routine for reading an applet param which is an integer.
		// Returns null if there is no such param, or if the value is not
		// a legal integer.
		String param = getParameter(paramName);
		if (param == null)
			return null;
		int i;
		try {
			i = Integer.parseInt(param);
		} catch (NumberFormatException e) {
			return null;
		}
		return new Integer(i);
	}

	Color getColorParam(String paramName) {
		// Utility routine for reading an applet param which is a color.
		// Returns null if there is no such param, or if the value is not
		// a legal color. Colors can be specified as three integers,
		// separated by spaces, giving RGB components in the range 0 to 255;
		// the standard Java color names are also acceptable.
		String param = getParameter(paramName);
		if (param == null || param.length() == 0)
			return null;
		if (Character.isDigit(param.charAt(0))) { // try to parse RGB color
			int r = 0, g = 0, b = 0;
			int pos = 0;
			int d = 0;
			int len = param.length();
			while (pos < len && Character.isDigit(param.charAt(pos)) && r < 255) {
				d = Character.digit(param.charAt(pos), 10);
				r = 10 * r + d;
				pos++;
			}
			if (r > 255)
				return null;
			while (pos < len && !Character.isDigit(param.charAt(pos)))
				pos++;
			if (pos >= len)
				return null;
			while (pos < len && Character.isDigit(param.charAt(pos)) && g < 255) {
				d = Character.digit(param.charAt(pos), 10);
				g = 10 * g + d;
				pos++;
			}
			if (g > 255)
				return null;
			while (pos < len && !Character.isDigit(param.charAt(pos)))
				pos++;
			if (pos >= len)
				return null;
			while (pos < len && Character.isDigit(param.charAt(pos)) && b < 255) {
				d = Character.digit(param.charAt(pos), 10);
				b = 10 * b + d;
				pos++;
			}
			if (b > 255)
				return null;
			return new Color(r, g, b);
		}
		if (param.equalsIgnoreCase("black"))
			return Color.black;
		if (param.equalsIgnoreCase("white"))
			return Color.white;
		if (param.equalsIgnoreCase("red"))
			return Color.red;
		if (param.equalsIgnoreCase("green"))
			return Color.green;
		if (param.equalsIgnoreCase("blue"))
			return Color.blue;
		if (param.equalsIgnoreCase("yellow"))
			return Color.yellow;
		if (param.equalsIgnoreCase("cyan"))
			return Color.cyan;
		if (param.equalsIgnoreCase("magenta"))
			return Color.magenta;
		if (param.equalsIgnoreCase("pink"))
			return Color.pink;
		if (param.equalsIgnoreCase("orange"))
			return Color.orange;
		if (param.equalsIgnoreCase("gray"))
			return Color.gray;
		if (param.equalsIgnoreCase("darkgray"))
			return Color.darkGray;
		if (param.equalsIgnoreCase("lightgray"))
			return Color.lightGray;
		return null; // param is not a legal color
	}

	public void init() {
		Integer parm;
		parm = getIntParam("rows");
		if (parm != null && parm.intValue() > 4 && parm.intValue() <= 500) {
			rows = parm.intValue();
			if (rows % 2 == 0)
				rows++;
		}
		parm = getIntParam("columns");
		if (parm != null && parm.intValue() > 4 && parm.intValue() <= 500) {
			columns = parm.intValue();
			if (columns % 2 == 0)
				columns++;
		}
		parm = getIntParam("border");
		if (parm != null && parm.intValue() > 0 && parm.intValue() <= 100)
			border = parm.intValue();
		parm = getIntParam("sleepTime");
		if (parm != null && parm.intValue() > 0)
			sleepTime = parm.intValue();
		parm = getIntParam("speed");
		if (parm != null && parm.intValue() > 0 && parm.intValue() < 6)
			switch (parm.intValue()) {
			case 1:
				speedSleep = 1;
				break;
			case 2:
				speedSleep = 25;
				break;
			case 3:
				speedSleep = 50;
				break;
			case 4:
				speedSleep = 100;
				break;
			case 5:
				speedSleep = 200;
				break;
			}
		color[Maze.wall] = getColorParam("wallColor");
		if (color[Maze.wall] == null)
			color[Maze.wall] = Color.black;
		color[Maze.path] = getColorParam("pathColor");
		if (color[Maze.path] == null)
			color[Maze.path] = new Color(200, 0, 0);
		color[Maze.empty] = getColorParam("emptyColor");
		if (color[Maze.empty] == null)
			color[Maze.empty] = new Color(128, 128, 255);
		color[backgroundCode] = getColorParam("borderColor");
		if (color[backgroundCode] == null)
			color[backgroundCode] = Color.white;
		color[Maze.visited] = getColorParam("visitedColor");
		if (color[Maze.visited] == null)
			color[Maze.visited] = color[Maze.empty];
		setBackground(color[backgroundCode]);
	}

	void checkSize() {
		// Called every time something is about to be drawn to
		// check the applet size and adjust variables that depend
		// on the size. This includes a graphics context for the
		// applet.
		if (this.getWidth() != width || this.getHeight() != height) {
			width = this.getWidth();
			height = this.getHeight();
			int w = (width - 2 * border) / columns;
			int h = (height - 2 * border) / rows;
			left = (width - w * columns) / 2;
			top = (height - h * rows) / 2;
			totalWidth = w * columns;
			totalHeight = h * rows;
			if (g != null)
				g.dispose(); // get rid of old graphics context
			g = getGraphics();
		}
	}

	public void start() {
		mazeThread = new Thread(this);
		mazeThread.start();
	}

	public void paint(Graphics g) {
		checkSize();
		drawMaze(g);
	}

	public void update(Graphics g) { // don't bother filling with background
										// color
		paint(g); // because redrawMaze() does that anyway
	}

	synchronized void drawMaze(Graphics g) {
		// draws the entire maze
		g.setColor(color[backgroundCode]);
		g.fillRect(0, 0, width, height);
		if (mazeExists) {
			int w = totalWidth / columns; // width of each cell
			int h = totalHeight / rows; // height of each cell
			for (int j = 0; j < columns; j++)
				for (int i = 0; i < rows; i++) {
					if (maze[i][j] < 0)
						g.setColor(color[Maze.empty]);
					else
						g.setColor(color[maze[i][j]]);
					g.fillRect((j * w) + left, (i * h) + top, w, h);
				}
		}
	}

	synchronized void putSquare(int row, int col, int colorNum) {
		// draw one cell of the maze, to the graphics context "me"
		checkSize();
		int w = totalWidth / columns; // width of each cell
		int h = totalHeight / rows; // height of each cell
		g.setColor(color[colorNum]);
		g.fillRect((col * w) + left, (row * h) + top, w, h);
	}

	public void run() {
		// run method for thread repeatedly makes a maze and then solves it
		try {
			Thread.sleep(2000);
		} // wait a bit before starting
		catch (InterruptedException e) {
		}
		while (true) {
			this.maze = Mazes.newMaze(this.rows, this.columns);
			mazeExists = true;
			checkSize();
			drawMaze(g);
			solveMaze(1, 1);
			try {
				Thread.sleep(sleepTime);
			} catch (InterruptedException e) {
			}
			mazeExists = false;
			checkSize();
			drawMaze(g); // erase old maze
		}
	}

	boolean solveMaze(int row, int col) {
		// Try to solve the maze by continuing current path from position
		// (row,col). Return true if a solution is found. The maze is
		// considered to be solved if the path reaches the lower right cell.
		if (maze[row][col] == Maze.empty) {
			maze[row][col] = Maze.path; // add this cell to the path
			putSquare(row, col, Maze.path);
			if (row == rows - 2 && col == columns - 2)
				return true; // path has reached goal
			try {
				Thread.sleep(speedSleep);
			} catch (InterruptedException e) {
			}
			if (solveMaze(row - 1, col) || // try to solve maze by extending
											// path
					solveMaze(row, col - 1) || // in each possible direction
					solveMaze(row + 1, col) || solveMaze(row, col + 1))
				return true;
			// maze can't be solved from this cell, so backtract out of the cell
			maze[row][col] = Maze.visited; // mark cell as having been visited
			putSquare(row, col, Maze.visited);
			try {
				Thread.sleep(speedSleep);
			} catch (InterruptedException e) {
			}
		}
		return false;
	}
}
